<?php
class ControllerCommunityCron extends Controller {
    private $error = array();

    public function index() {
        $this->load->language('community/cron');

        $this->load->model('community/cron');

        $this->document->setTitle($this->language->get('heading_title'));

        if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validateForm()) {
            $this->model_community_cron->updateCrons($this->request->post);

            //clear cron caceh
            @unlink(DIR_CACHE . 'sc/cache.nextrun');

            $this->session->data['success'] = $this->language->get('text_success');

            $this->redirect($this->url->link('community/cron', 'token=' . $this->session->data['token'], 'SSL'));
        }

        $this->data['heading_title'] = $this->language->get('heading_title');

        $this->data['text_never'] = $this->language->get('text_never');
        $this->data['text_enabled'] = $this->language->get('text_enabled');
        $this->data['text_disabled'] = $this->language->get('text_disabled');

        $this->data['entry_name'] = $this->language->get('entry_name');
        $this->data['entry_file_name'] = $this->language->get('entry_file_name');
        $this->data['entry_status'] = $this->language->get('entry_status');
        $this->data['entry_log'] = $this->language->get('entry_log');
        $this->data['entry_execute_time'] = $this->language->get('entry_execute_time');
        $this->data['entry_last_execute_time'] = $this->language->get('entry_last_execute_time');
        $this->data['entry_next_execute_time'] = $this->language->get('entry_next_execute_time');

        $this->data['button_save'] = $this->language->get('button_save');
        $this->data['button_cancel'] = $this->language->get('button_cancel');
        $this->data['button_add'] = $this->language->get('button_add');
        $this->data['button_remove'] = $this->language->get('button_remove');
        $this->data['button_edit'] = $this->language->get('button_edit');
        $this->data['button_execute'] = $this->language->get('button_execute');

        if (isset($this->error['warning'])) {
            $this->data['error_warning'] = $this->error['warning'];
        } else {
            $this->data['error_warning'] = '';
        }

        if (isset($this->error['cron_name'])) {
            $this->data['error_cron_name'] = $this->error['cron_name'];
        } else {
            $this->data['error_cron_name'] = array();
        }

        if (isset($this->error['cron_file_name'])) {
            $this->data['error_cron_file_name'] = $this->error['cron_file_name'];
        } else {
            $this->data['error_cron_file_name'] = array();
        }

        $this->data['breadcrumbs'] = array();

        $this->data['breadcrumbs'][] = array(
            'text'      => $this->language->get('text_home'),
            'href'      => $this->url->link('common/home', 'token=' . $this->session->data['token'], 'SSL'),
            'separator' => false
        );

        $this->data['breadcrumbs'][] = array(
            'text'      => $this->language->get('heading_title'),
            'href'      => $this->url->link('community/cron', 'token=' . $this->session->data['token'], 'SSL'),
            'separator' => ' :: '
        );

        $this->data['cancel'] = $this->url->link('community/cron', 'token=' . $this->session->data['token'], 'SSL');

        if (isset($this->request->post['cron'])) {
            $cron_data = $this->request->post['cron'];
        } else {
            $cron_data = $this->model_community_cron->getCrons();
        }

        $this->data['crons']=array();

        foreach($cron_data as $cron){
            $this->data['crons'][]=array(
                'cron_id'      =>      $cron['cron_id'],
                'available'    =>      $cron['available'],
                'name'         =>      $cron['name'],
                'file_name'    =>      $cron['file_name'],
                'last_run_text'=>      ($cron['last_run'] ? date('Y-m-d H:i:s', $cron['last_run']) : $this->language->get('text_never')),
                'last_run'     =>      $cron['last_run'],
                'next_run_text'=>      ($cron['next_run'] ? date('Y-m-d H:i:s', $cron['next_run']) : $this->language->get('text_never')),
                'next_run'     =>      $cron['next_run'],
                'execute_time' =>      $this->formatTime($cron),
                'month'        =>      $cron['month'],
                'weekday'      =>      $cron['weekday'],
                'day'          =>      $cron['day'],
                'hour'         =>      $cron['hour'],
                'minute'       =>      $cron['minute'],
                'logging'      =>      $cron['logging'],
                'cron_edit'    =>     $this->url->link('community/cron/edit', 'token=' . $this->session->data['token'] . '&cron_id=' . $cron['cron_id'], 'SSL'),
                'cron_execute' =>     $this->url->link('community/cron/execute&cronid=' .  $cron['cron_id'], 'token=' . $this->session->data['token'], 'SSL')
            );
        }

        if (isset($this->session->data['success'])) {
            $this->data['success'] = $this->session->data['success'];

            unset($this->session->data['success']);
        } else {
            $this->data['success'] = '';
        }

        $this->template = 'community/cron_list.tpl';
        $this->children = array(
            'common/header',
            'common/footer'
        );

        $this->response->setOutput($this->render());
    }

    public function edit(){
        $this->load->language('community/cron');

        $this->load->model('community/cron');

        $this->document->setTitle($this->language->get('heading_title'));

        if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validateForm()) {
        $this->model_community_cron->updateCron($this->request->post, $this->request->get, $this->setNextTime($this->request->post['cron']));

        //clear cron caceh
        if(file_exists(DIR_CACHE . 'sc/cache.nextrun')){
            unlink(DIR_CACHE . 'sc/cache.nextrun');
        }

        $this->session->data['success'] = $this->language->get('text_success');

        $this->redirect($this->url->link('community/cron', 'token=' . $this->session->data['token'], 'SSL'));
        }

        $cron=$this->model_community_cron->getCron($this->request->get);

        $this->data['heading_title'] = $this->language->get('heading_title');

        $this->data['text_edit_cron'] = sprintf($this->language->get('text_edit_cron'),$cron['name']);

        $this->data['entry_month'] = $this->language->get('entry_month');
        $this->data['entry_week'] = $this->language->get('entry_week');
        $this->data['entry_day'] = $this->language->get('entry_day');
        $this->data['entry_hour'] = $this->language->get('entry_hour');
        $this->data['entry_minute'] = $this->language->get('entry_minute');
        $this->data['entry_file_name'] = $this->language->get('entry_file_name');

        $this->data['button_save'] = $this->language->get('button_save');
        $this->data['button_cancel'] = $this->language->get('button_cancel');

        if (isset($this->error['warning'])) {
            $this->data['error_warning'] = $this->error['warning'];
        } else {
            $this->data['error_warning'] = '';
        }

        if (isset($this->error['cron_month'])) {
            $this->data['error_cron_month'] = $this->error['cron_month'];
        } else {
            $this->data['error_cron_month'] = '';
        }

        if (isset($this->error['cron_weekday'])) {
            $this->data['error_cron_weekday'] = $this->error['cron_weekday'];
        } else {
            $this->data['error_cron_weekday'] = '';
        }

        if (isset($this->error['cron_day'])) {
            $this->data['error_cron_day'] = $this->error['cron_day'];
        } else {
            $this->data['error_cron_day'] = '';
        }

        if (isset($this->error['cron_hour'])) {
            $this->data['error_cron_hour'] = $this->error['cron_hour'];
        } else {
            $this->data['error_cron_hour'] = '';
        }

        if (isset($this->error['cron_minute'])) {
            $this->data['error_cron_minute'] = $this->error['cron_minute'];
        } else {
            $this->data['error_cron_minute'] = '';
        }

        if (isset($this->error['cron_file_name'])) {
            $this->data['error_cron_file_name'] = $this->error['cron_file_name'];
        } else {
            $this->data['error_cron_file_name'] = '';
        }

        $this->data['breadcrumbs'] = array();

        $this->data['breadcrumbs'][] = array(
            'text'      => $this->language->get('text_home'),
            'href'      => $this->url->link('common/home', 'token=' . $this->session->data['token'], 'SSL'),
            'separator' => false
        );

        $this->data['breadcrumbs'][] = array(
            'text'      => $this->language->get('heading_title'),
            'href'      => $this->url->link('community/cron', 'token=' . $this->session->data['token'], 'SSL'),
            'separator' => ' :: '
        );

        $this->data['breadcrumbs'][] = array(
            'text'      => sprintf($this->language->get('text_edit_cron'),$cron['name']),
            'href'      => $this->url->link('community/cron/edit', 'token=' . $this->session->data['token'] . '&cron_id=' . $this->request->get['cron_id'], 'SSL'),
            'separator' => ' :: '
        );

        $this->data['cancel'] = $this->url->link('community/cron', 'token=' . $this->session->data['token'], 'SSL');

        if (isset($this->request->post['cron'])) {
            $this->data['cron'] = $this->request->post['cron'];
        } else {
            $this->data['cron'] = $cron;
        }

        $this->template = 'community/cron_form.tpl';
        $this->children = array(
            'common/header',
            'common/footer'
        );

        $this->response->setOutput($this->render());
    }

    private function validateForm() {
        if (!$this->user->hasPermission('modify', 'community/cron')) {
            $this->error['warning'] = $this->language->get('error_permission');
        }

        if(isset($this->request->get['cron_id'])){
            if(!file_exists(DIR_SYSTEM . 'cron/' . trim($this->request->post['cron']['file_name']))){
                $this->error['cron_file_name'] = $this->language->get('error_cron_file_not_exist');
            }

            if ((utf8_strlen($this->request->post['cron']['file_name']) < 1) || (utf8_strlen($this->request->post['cron']['file_name']) > 50)) {
                $this->error['cron_file_name'] = $this->language->get('error_cron_file_name');
            }

            //month
            $time_array=explode(',', $this->request->post['cron']['month']);

            $previous=0;

            foreach($time_array as $time){
                if(!((is_numeric($time) && $time>0 && $time<13) || $time='*')){
                    $this->error['cron_month'] = $this->language->get('error_cron_time_format');
                }

                if($time<$previous){
                    $this->error['cron_month'] = $this->language->get('error_cron_time_format');
                }

                $previous=$time;
            }

            if($this->request->post['cron']['month']==''){
                $this->error['cron_month'] = $this->language->get('error_cron_time_empty');
            }

            //weekday
            $time_array=explode(',', $this->request->post['cron']['weekday']);

            $previous=0;

            foreach($time_array as $time){
                if(!((is_numeric($time) && $time>-1 && $time<7) || $time='*')){
                    $this->error['cron_weekday'] = $this->language->get('error_cron_time_format');
                }

                if($time<$previous){
                    $this->error['cron_weekday'] = $this->language->get('error_cron_time_format');
                }

                $previous=$time;
            }

            if($this->request->post['cron']['weekday']==''){
                $this->error['cron_weekday'] = $this->language->get('error_cron_time_empty');
            }

            //day
            $time_array=explode(',', $this->request->post['cron']['day']);

            $previous=0;

            foreach($time_array as $time){
                if(!((is_numeric($time) && $time>0 && $time<32) || $time='*')){
                    $this->error['cron_day'] = $this->language->get('error_cron_time_format');
                }

                if($time<$previous){
                    $this->error['cron_day'] = $this->language->get('error_cron_time_format');
                }

                $previous=$time;
            }

            if($this->request->post['cron']['day']==''){
                $this->error['cron_day'] = $this->language->get('error_cron_time_empty');
            }

            //hour
            $time_array=explode(',', $this->request->post['cron']['hour']);

            $previous=0;

            foreach($time_array as $time){
                if(!((is_numeric($time) && $time>-1 && $time<24) || $time='*')){
                    $this->error['cron_hour'] = $this->language->get('error_cron_time_format');
                }

                if($time<$previous){
                    $this->error['cron_hour'] = $this->language->get('error_cron_time_format');
                }

                $previous=$time;
            }

            if($this->request->post['cron']['hour']==''){
                $this->error['cron_hour'] = $this->language->get('error_cron_time_empty');
            }

            //minute
            $time_array=explode(',', $this->request->post['cron']['minute']);

            $previous=0;

            foreach($time_array as $time){
                if(!((is_numeric($time) && $time>-1 && $time<60) || $time='*')){
                    $this->error['cron_minute'] = $this->language->get('error_cron_time_format');
                }

                if($time<$previous){
                    $this->error['cron_minute'] = $this->language->get('error_cron_time_format');
                }

                $previous=$time;
            }

            if($this->request->post['cron']['minute']==''){
                $this->error['cron_minute'] = $this->language->get('error_cron_time_empty');
            }
        }else{
            foreach($this->request->post['cron'] as $row_id => $cron){
                if ((utf8_strlen($cron['name']) < 1) || (utf8_strlen($cron['name']) > 50)) {
                    $this->error['cron_name'][$row_id] = $this->language->get('error_cron_name');
                }
            }

            foreach($this->request->post['cron'] as $row_id => $cron){
                if(!file_exists(DIR_SYSTEM . 'cron/' . trim($cron['file_name']))){
                    $this->error['cron_file_name'][$row_id] = $this->language->get('error_cron_file_not_exist');
                }

                if ((utf8_strlen($cron['file_name']) < 1) || (utf8_strlen($cron['file_name']) > 50)) {
                    $this->error['cron_file_name'][$row_id] = $this->language->get('error_cron_file_name');
                }
            }
        }

        if ($this->error && !isset($this->error['warning'])) {
            $this->error['warning'] = $this->language->get('error_warning');
        }

        if (!$this->error) {
            return true;
        } else {
            return false;
        }
    }

    private function formatTime($cron){
        $this->load->language('community/cron');

        if($cron['month']!='*'){
            $months=explode(",",$cron['month']);
            $str='';

            foreach($months as $month){
                $str.=$month . ', ';
            }

            $str=trim($str);
            $str=rtrim($str,',');

            $month_str=sprintf($this->language->get('text_month_nums'),$str);
        }else{
            $month_str=$this->language->get('text_monthly');
        }

        if($cron['weekday']!='*'){
            $weekdays=explode(",",$cron['weekday']);
            $str='';

            foreach($weekdays as $weekday){
                switch($weekday){
                    case 0:
                        $weekday=$this->language->get('text_sunday');
                        break;
                    case 1:
                        $weekday=$this->language->get('text_monday');
                        break;
                    case 2:
                        $weekday=$this->language->get('text_tuesday');
                        break;
                    case 3:
                        $weekday=$this->language->get('text_wednesday');
                        break;
                    case 4:
                        $weekday=$this->language->get('text_thursday');
                        break;
                    case 5:
                        $weekday=$this->language->get('text_friday');
                        break;
                    default:
                        $weekday=$this->language->get('text_saturday');
                }

                $str.=$weekday . ', ';
            }

            $str=trim($str);
            $str=rtrim($str,',');

            $weekday_str=sprintf($this->language->get('text_weekdays'),$str);
        }else{
            $weekday_str=$this->language->get('text_every_weekday');
        }

        if($cron['day']!='*'){
            $days=explode(",",$cron['day']);
            $str='';

            foreach($days as $day){
                $str.=$day . ', ';
            }

            $str=trim($str);
            $str=rtrim($str,',');

            $day_str=sprintf($this->language->get('text_day_nums'),$str);
        }else{
            $day_str=$this->language->get('text_daily');
        }

        if($cron['hour']!='*'){
            $hours=explode(",",$cron['hour']);
            $str='';

            foreach($hours as $hour){
                $str.=str_pad($hour, 2, '0', STR_PAD_LEFT) . ', ';
            }

            $str=trim($str);
            $str=rtrim($str,',');

            $hour_str=sprintf($this->language->get('text_hour_nums'),$str);
        }else{
            $hour_str=$this->language->get('text_hourly');
        }

        if($cron['minute']!='*'){
            $minutes=explode(",",$cron['minute']);
            $str='';

            foreach($minutes as $minute){
                $str.=str_pad($minute, 2, '0', STR_PAD_LEFT) . ', ';
            }

            $str=trim($str);
            $str=rtrim($str,',');

            $minute_str=sprintf($this->language->get('text_minute_nums'),$str);
        }else{
            $minute_str=$this->language->get('text_minutely');
        }

        return ($month_str . '<br />' . $weekday_str . '<br />' . $day_str . '<br />' . $hour_str . '<br />' . $minute_str);
    }

    private function setNextTime($cron){
        $time = time();
        $reset_hour=0;
        $reset_day=0;
        $reset_month=0;
        $reset_year=0;
        $next_minute = $current_minute = date("i", $time);
        $next_hour = $current_hour = date("H", $time);
        $next_day = $current_day = date("d", $time);
        $next_weekday = $current_weekday = date("w", $time);
        $next_month = $current_month = date("m", $time);
        $next_year = $current_year = date("Y", $time);

        if($cron['minute'] == "*"){
            ++$next_minute;
            if($next_minute > 59){
                $reset_hour = 1;
                $next_minute = 0;
            }
        }else{
            if($this->buildNextRunBit($cron['minute'], $current_minute) != false){
                $next_minute = $this->buildNextRunBit($cron['minute'], $current_minute);
            }else{
                $next_minute = $this->fetchFirstRunTime($cron['minute']);
            }

            if($next_minute <= $current_minute){
                $reset_hour = 1;
            }
        }

        if($reset_hour || !$this->runTimeExists($cron['hour'], $current_hour)){
            if($cron['hour'] == "*"){
                ++$next_hour;
                if($next_hour > 23){
                    $reset_day = 1;
                    $next_hour = 0;
                }
            }else{
                if($this->buildNextRunBit($cron['hour'], $current_hour) != false){
                    $next_hour = $this->buildNextRunBit($cron['hour'], $current_hour);
                }else{
                    $next_hour = $this->fetchFirstRunTime($cron['hour']);
                    $reset_day = 1;
                }

                if($next_hour < $current_hour){
                    $reset_day = 1;
                }
            }

            $next_minute = $this->fetchFirstRunTime($cron['minute']);
        }

        if($reset_day || ($cron['weekday'] == "*" && !$this->runTimeExists($cron['day'], $current_day) || $cron['day'] == "*" && !$this->runTimeExists($cron['weekday'], $current_weekday))){
            if($cron['weekday'] == "*"){
                if($cron['day'] == "*"){
                    ++$next_day;
                    if($next_day > date("t", $time)){
                        $reset_month = 1;
                        $next_day = 1;
                    }
                }else{
                    if($this->buildNextRunBit($cron['day'], $current_day) != false){
                        $next_day = $this->buildNextRunBit($cron['day'], $current_day);
                    }else{
                        $next_day = $this->fetchFirstRunTime($cron['day']);
                        $reset_month = 1;
                    }if($next_day < $current_day){
                        $reset_month = 1;
                    }
                }
            }else{
                if($this->buildNextRunBit($cron['weekday'], $current_weekday) != false){
                    $next_weekday = $this->buildNextRunBit($cron['weekday'], $current_weekday);
                }else{
                    $next_weekday = $this->fetchFirstRunTime($cron['weekday']);
                }

                $next_day = $current_day + ($next_weekday-$current_weekday);

                if($next_day <= $current_day){
                    $next_day += 7;
                }

                if($next_day > date("t", $time)){
                    $reset_month = 1;
                }
            }
            $next_minute = $this->fetchFirstRunTime($cron['minute']);
            $next_hour = $this->fetchFirstRunTime($cron['hour']);

            if($next_day == $current_day && $next_hour < $current_hour){
                $reset_month = 1;
            }
        }

        if($reset_month || !$this->runTimeExists($cron['month'], $current_month)){
            if($cron['month'] == "*"){
                $next_month++;
                if($next_month > 12){
                    $reset_year = 1;
                    $next_month = 1;
                }
            }else{
                if($this->buildNextRunBit($cron['month'], $current_month) != false){
                    $next_month = $this->buildNextRunBit($cron['month'], $current_month);
                }else{
                    $next_month = $this->fetchFirstRunTime($cron['month']);
                    $reset_year = 1;
                }

                if($next_month < $current_month){
                    $reset_year = 1;
                }
            }

            $next_minute = $this->fetchFirstRunTime($cron['minute']);
            $next_hour = $this->fetchFirstRunTime($cron['hour']);

            if($cron['weekday'] == "*"){
                $next_day = $this->fetchFirstRunTime($cron['day']);
                if($next_day == 0) $next_day = 1;
            }else{
                $next_weekday = $this->fetchFirstRunTime($cron['weekday']);
                $new_weekday = date("w", mktime($next_hour, $next_minute, 0, $next_month, 1, $next_year));
                $next_day = 1 + ($next_weekday-$new_weekday);

                if($next_weekday < $new_weekday){
                    $next_day += 7;
                }
            }

            if($next_month == $current_month && $next_day == $current_day && $next_hour < $current_hour){
                $reset_year = 1;
            }
        }

        if($reset_year){
            $next_year++;
            $next_minute = $this->fetchFirstRunTime($cron['minute']);
            $next_hour = $this->fetchFirstRunTime($cron['hour']);
            $next_month = $this->fetchFirstRunTime($cron['month']);

            if($next_month == 0) $next_month = 1;

            if($cron['weekday'] == "*"){
                $next_day = $this->fetchFirstRunTime($cron['day']);
                if($next_day == 0) $next_day = 1;
            }else{
                $next_weekday = $this->fetchFirstRunTime($cron['weekday']);
                $new_weekday = date("w", mktime($next_hour, $next_minute, 0, $next_month, 1, $next_year));
                $next_day = 1 + ($next_weekday-$new_weekday);

                if($next_weekday < $new_weekday){
                    $next_day += 7;
                }
            }
        }

        return mktime($next_hour, $next_minute, 0, $next_month, $next_day, $next_year);

    }

    /**
     * Builds the next run time bit for a particular item (day, hour, month etc). Used by setNextTime().
     *
     * @param string A string containing the run timse for this particular item
     * @param int The current value (be it current day etc)
     * @return int The new or found value
     */
    private function buildNextRunBit($data, $bit){
        if($data == "*") return $bit;
        $data = explode(",", $data);

        foreach($data as $thing){
            if($thing > $bit){
                return $thing;
            }
        }

        return false;
    }

    /**
     * Fetches the fist run bit for a particular item (day, hour, month etc). Used by setNextTime().
     *
     * @param string A string containing the run times for this particular item
     * @return int The first run time
     */
    private function fetchFirstRunTime($data){
        if($data == "*") return "0";
        $data = explode(",", $data);
        return $data[0];
    }

    /**
     * Checks if a specific run time exists for a particular item (day, hour, month etc). Used by setNextTime().
     *
     * @param string A string containing the run times for this particular item
     * @param int The bit we're checking for
     * @return boolean True if it exists, false if it does not
     */
    private function runTimeExists($data, $bit){
        if($data == "*") return true;
        $data = explode(",", $data);

        if(in_array($bit, $data)){
            return true;
        }

        return false;
    }
}
?>